﻿using Caliburn.Micro;
using Domain;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common.Extensions;
using Common.Utils;
using Common.UI.Controls;
using Windows.UI.Popups;
using Common.UI.Controls.Utils;
using Windows.UI.Xaml;

namespace GYMm.UI.ViewModels
{
	public class ExerciseViewModel : Screen
	{
		#region PROPS
        private DispatcherTimer _timer = new DispatcherTimer();

		public const Double MAX_VOLUME = 5000;
		public Exercise Parameter { get; set; }

		private DoubleBarChartItemsSource _barItems;
		public DoubleBarChartItemsSource BarItems
		{
			get { return _barItems; }
			set
			{
				if (_barItems != value)
				{
					_barItems = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private ObjectSource _abscItems;
		public ObjectSource AbscItems
		{
			get { return _abscItems; }
			set
			{
				if (_abscItems != value)
				{
					_abscItems = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Boolean _isRecordWeightWorkout;
		public Boolean IsRecordWeightWorkout
		{
			get { return _isRecordWeightWorkout; }
			set
			{
				if (_isRecordWeightWorkout != value)
				{
					_isRecordWeightWorkout = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Boolean _isRecordVolumeWorkout;
		public Boolean IsRecordVolumeWorkout
		{
			get { return _isRecordVolumeWorkout; }
			set
			{
				if (_isRecordVolumeWorkout != value)
				{
					_isRecordVolumeWorkout = value;
					NotifyOfPropertyChange();
				}
			}
		}

		//private DoubleSource _weightHistory;
		//public DoubleSource WeightHistory
		//{
		//	get { return _weightHistory; }
		//	set
		//	{
		//		if (_weightHistory != value)
		//		{
		//			_weightHistory = value;
		//			NotifyOfPropertyChange(() => WeightHistory);
		//		}
		//	}
		//}

		private DoubleSource _volumeHistory;
		public DoubleSource VolumeHistory
		{
			get { return _volumeHistory; }
			set
			{
				if (_volumeHistory != value)
				{
					_volumeHistory = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private DateTimeChartItemsSource _weightIntervals;
		public DateTimeChartItemsSource WeightIntervals
		{
			get { return _weightIntervals; }
			set
			{
				if (_weightIntervals != value)
				{
					_weightIntervals = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private DateTime _lastExerciseLogDateTime;
		public DateTime LastExerciseLogDateTime
		{
			get { return _lastExerciseLogDateTime; }
			set
			{
				if (_lastExerciseLogDateTime != value)
				{
					_lastExerciseLogDateTime = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private DateTime _presentExerciseLogDateTime;
		public DateTime PresentExerciseLogDateTime
		{
			get { return _presentExerciseLogDateTime; }
			set
			{
				if (_presentExerciseLogDateTime != value)
				{
					_presentExerciseLogDateTime = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Int32 _workoutsCount;
		public Int32 WorkoutsCount
		{
			get { return _workoutsCount; }
			set
			{
				if (_workoutsCount != value)
				{
					_workoutsCount = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Double _recordVolume;
		public Double RecordVolume
		{
			get { return _recordVolume; }
			set
			{
				if (_recordVolume != value)
				{
					_recordVolume = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Int32 _totalPrev;
		public Int32 LastVolume
		{
			get { return _totalPrev; }
			set
			{
				if (_totalPrev != value)
				{
					_totalPrev = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Double _volume;
		public Double Volume
		{
			get { return _volume; }
			set
			{
				if (_volume != value)
				{
					_volume = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Double _recordWeight;
		public Double RecordWeight
		{
			get { return _recordWeight; }
			set
			{
				if (_recordWeight != value)
				{
					_recordWeight = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Double _lastWeight;
		public Double LastWeight
		{
			get { return _lastWeight; }
			set
			{
				if (_lastWeight != value)
				{
					_lastWeight = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Double _weight;
		public Double Weight
		{
			get { return _weight; }
			set
			{
				if (_weight != value)
				{
					_weight = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private String _exerciseDisplayName;
		public String ExerciseDisplayName
		{
			get { return _exerciseDisplayName; }
			set
			{
				if (_exerciseDisplayName != value)
				{
					_exerciseDisplayName = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Boolean _setLogMode;
		public Boolean IsSetLogMode
		{
			get { return _setLogMode; }
			set
			{
				if (_setLogMode != value)
				{
					_setLogMode = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Boolean _isFirstWorkout;
		public Boolean IsFirstWorkout
		{
			get { return _isFirstWorkout; }
			set
			{
				if (_isFirstWorkout != value)
				{
					_isFirstWorkout = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Double _selectedWeight;
		public Double SelectedWeight
		{
			get { return _selectedWeight; }
			set
			{
				if (_selectedWeight != value)
				{
					_selectedWeight = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Double _selectedReps;
		public Double SelectedReps
		{
			get { return _selectedReps; }
			set
			{
				if (_selectedReps != value)
				{
					_selectedReps = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Models.GymModel _model;

		private Exercise _exercise;

		private SetLog _selectedSetLog;
		public SetLog SelectedSetLog
		{
			get { return _selectedSetLog; }
			set
			{
				if (_selectedSetLog != value)
				{
					_selectedSetLog = value;
					NotifyOfPropertyChange();
				}
			}
		}

		private Int32 _selectedExerciseLogIndex;
		public Int32 SelectedExerciseLogIndex
		{
			get { return _selectedExerciseLogIndex; }
			set
			{
				if (_selectedExerciseLogIndex != value)
				{
					_selectedExerciseLogIndex = value;
					NotifyOfPropertyChange();
				}
			}
		}

        private TimeSpan _timeElapsed;

        public TimeSpan TimeElapsed
        {
            get { return _timeElapsed; }
            set { _timeElapsed = value; NotifyOfPropertyChange(); }
        }


		private ExerciseLog _curExerciseLog;
		private ExerciseLog _prevExerciseLog;

		public ExerciseViewModel(Models.GymModel model)
		{
			_model = model;
            _timer.Interval = TimeSpan.FromMilliseconds(100);
            _timer.Tick += _timer_Tick;
            _timer.Start();
		}

        void _timer_Tick(object sender, object e)
        {
            if (_exercise == null) return;

            var lastLog = _exercise.GetLastLog();
            if (lastLog == null) return;

            TimeElapsed = lastLog.LastSetElapsedTime();
        }
		#endregion

		protected override async void OnActivate()
		{
			base.OnActivate();
			_exercise = Parameter;
			await InitAsync(load: false);
		}

		protected override void OnDeactivate(bool close)
		{
			//await SaveAll();
			base.OnDeactivate(close);
		}



		private async Task InitAsync(Boolean load = true)
		{
			if (load)
				_exercise = await _model.LoadExerciseByIdAsync(_exercise.Id);
			Update();
		}

		private void SetBla()
		{
			var index = SelectedExerciseLogIndex;
			if (_exercise.ExerciseLogs.IsNullOrEmpty())
			{
				_curExerciseLog = ExerciseLog.GetEmpty();
				_prevExerciseLog = ExerciseLog.GetEmpty();
				return;
			}

			var exerciseLogs = _exercise.ExerciseLogs.OrderByDescending(x => x.LogDate).ToList();

			_curExerciseLog = exerciseLogs[index];

			var prevIndex = index + 1;

			if (prevIndex < exerciseLogs.Count)
				_prevExerciseLog = exerciseLogs[prevIndex];
			else
				_prevExerciseLog = ExerciseLog.GetEmpty();

		}

		private void Update()
		{
			SetBla();
			IsFirstWorkout = _exercise.ExerciseLogs.Count == 0;
			ExerciseDisplayName = _exercise.DisplayName;
			LastExerciseLogDateTime = _prevExerciseLog.LogDate;
			PresentExerciseLogDateTime = _curExerciseLog.LogDate;
			WorkoutsCount = _exercise.ExerciseLogs.Count;
			var recordVolume = _exercise.GetRecordVolume();
			RecordVolume = recordVolume;
			LastVolume = _prevExerciseLog.GetVolume();
			var volume = _curExerciseLog.GetVolume();
			Volume = volume;
			var recordWeight = _exercise.GetRecordWeight();
			RecordWeight = recordWeight;
			LastWeight = _prevExerciseLog.MaxWeight();
			var maxWeight = _curExerciseLog.MaxWeight();
			Weight = maxWeight;
			IsRecordWeightWorkout = Math.Abs(recordWeight - maxWeight) < 0.01;
			IsRecordVolumeWorkout = Math.Abs(recordVolume - volume) < 0.01;

			BarItems = new DoubleBarChartItemsSource(
				_curExerciseLog.Sets.OrderBy(x => x.EndDateTime).Select(x => new BarItem<Double>(x.Id, x.Weight)).ToList(),
				_prevExerciseLog.Sets.OrderBy(x => x.EndDateTime).Select(x => new BarItem<Double>(x.Id, x.Weight)).ToList());

			//WeightHistory = new DoubleSource() { Items = GetHistory(_exercise.ExerciseLogs, x => x.Weight) };
			VolumeHistory = new DoubleSource() { Items = GetHistory(_exercise.ExerciseLogs, x => x.GetTotal()) };
			WeightIntervals = new DateTimeChartItemsSource(_curExerciseLog.Sets.ToDictionary(x => x.EndDateTime, x => x.Weight));

			var reps = new IntBarChartItemsSource(
				_curExerciseLog.Sets.OrderBy(x => x.EndDateTime).Select(x => new BarItem<Int32>(x.Id, x.Repetitions)).ToList(),
				_prevExerciseLog.Sets.OrderBy(x => x.EndDateTime).Select(x => new BarItem<Int32>(x.Id, x.Repetitions)).ToList());

			AbscItems = new ObjectSource()
			{
				Items = reps.Count > 40 ? null : reps.ForEach<String>(
				(res, curReps, prevReps, i, isLast) =>
				{
					res.Add(RepsToString(curReps.Value, prevReps.Value, light: reps.Count > 10));
				})
			};
		}

		private static string RepsToString(double value, double backValue, Boolean light = false)
		{
			if (light)
				return value.ToString();

			if (IsZero(value) && !IsZero(backValue))
				return backValue.ToString();
			if (!IsZero(value) && IsZero(backValue))
				return value.ToString();
			if (!IsZero(value) && !IsZero(backValue))
				return String.Format("{0}|{1}", backValue, value);

			return "--";
		}

		private static Boolean IsZero(double value)
		{
			return value < 0.01;
		}

		private List<double> GetHistory(List<ExerciseLog> list, Func<SetLog, Double> selector)
		{
			var res = new List<Double>();
			foreach (var log in list.OrderBy(x => x.LogDate))
			{
				res.AddRange(log.Sets.Select(selector));
			}
			return res;
		}

		public void GoToNext()
		{
			if (IsSetLogMode)
				return;

			var val = SelectedExerciseLogIndex - 1;
			SetNewSelectedExerciseLogIndex(val);
		}

		private void SetNewSelectedExerciseLogIndex(int val)
		{
			if (val >= 0 && val < _exercise.ExerciseLogs.Count)
			{
				SelectedExerciseLogIndex = val;
				Update();
			}
		}

		public void GoToPrev()
		{
			if (IsSetLogMode)
				return;

			var val = SelectedExerciseLogIndex + 1;
			SetNewSelectedExerciseLogIndex(val);
		}

		public void GoToDashboard()
		{
			IoC.Get<INavigationService>().NavigateToViewModel<DashboardViewModel>();
		}

		public void ShowAddSetLogDialog()
		{
			SelectedSetLog = null;
			IsSetLogMode = true;
		}

		public void ShowAddSetLogDialog(Guid setId)
		{
			SelectedSetLog = _curExerciseLog.Sets.FirstOrDefault(x => x.Id == setId) ?? _prevExerciseLog.Sets.FirstOrDefault(x => x.Id == setId);
			SelectedWeight = SelectedSetLog.Weight;
			SelectedReps = SelectedSetLog.Repetitions;
			IsSetLogMode = true;
		}

		public void HideAddSetLogDialog()
		{
			SelectedSetLog = null;
			IsSetLogMode = false;

			ResetSelection();
		}

		public async Task SaveSetLog(Double weight, Int32 reps)
		{
			if (SelectedExerciseLogIndex != 0)
			{
				await ShowPastProhibitedMEssage();
				return;
			}

			if (IsZero(weight) || reps < 1 || weight * reps > MAX_VOLUME)
			{
				IsSetLogMode = false;
				var md = new MessageDialog("Please provide valid values of weight and repetitions");
				await md.ShowAsync();
				return;
			}

			if (SelectedSetLog != null)
			{
				SelectedSetLog.Weight = weight;
				SelectedSetLog.Repetitions = reps;
				await _model.UpdateSetLogAsync(SelectedSetLog);
			}
			else
			{
				var lastExerciseLog = _exercise.GetLastLog();

                if (lastExerciseLog.Id == Guid.Empty || lastExerciseLog.IsCompleted())
					lastExerciseLog = await CreateNewExerciseLog(_exercise);

				await CreateNewSetLog(lastExerciseLog.Id, weight, reps);
			}


			await InitAsync();
			HideAddSetLogDialog();
		}

		private async Task ShowPastProhibitedMEssage()
		{
			var md = new MessageDialog("You can't log sets in the past!");
			await md.ShowAsync();
		}

		private async Task CreateNewSetLog(Guid exerciseLogId, Double weight, Int32 reps)
		{
			var setLog = new SetLog()
			{
				Weight = weight,
				Repetitions = reps,
				EndDateTime = DateTimeHelper.Now
			};

			await _model.SaveSetLogAsync(exerciseLogId, setLog);
		}

		private async Task<ExerciseLog> CreateNewExerciseLog(Exercise exercise)
		{
			var log = new ExerciseLog()
			{
				LogDate = DateTimeHelper.Now,
				Sets = new List<SetLog>(),
			};
			await _model.CreateExerciseLogAsync(exercise.Id, log);
			return log;
		}

		public async Task RemoveSelectedSetLogAsync()
		{
			if (SelectedExerciseLogIndex != 0)
			{
				await ShowPastProhibitedMEssage();
				return;
			}

			ResetSelection();

			await MessageDialogHelper.ShowDialogAsync("CONFIRMATION", "Do you really want to remove the exercise set log?",
					new UICommand("Remove", x =>
					{
						RemoveSelectedSetLog();
					}));
		}

		private void ResetSelection()
		{
			var lastSet = _exercise.GetLastLog().Sets.OrderByDescending(x => x.EndDateTime).FirstOrDefault();
			SelectedWeight = lastSet == null ? 0.0 : lastSet.Weight;
			SelectedReps = lastSet == null ? 0.0 : lastSet.Repetitions;
		}

		private async void RemoveSelectedSetLog()
		{
			await _model.RemoveSetLogAsync(SelectedSetLog);
			await InitAsync();

			HideAddSetLogDialog();
		}


	}
}
